#	m42-xmas-memory
#   Copyright (C) 2014  Jannik Haberbosch
#
#	This program is free software: you can redistribute it and/or modify
#	it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.


import sqlite3
import random
import os
import os.path

import pygame
pygame.init()


from code.constants import general
from code.constants import colours
from code.constants import fonts
from code.constants import main_menu_data
from code.constants import option_menu_data
from code.constants import pre_game_setup_data
from code.constants import pre_game_setup_names_of_players_data
from code.constants import return_codes
from code.constants import pre_game_setup_board_size as pregame_setup_boardsize
from code.constants import setup_game_paused
from code.constants import game_over_scene_data

from code.classes.Text import Text
from code.classes.Button import Button
from code.classes.EnableDisableOption import EnableDisableOption
from code.classes.UserInputText import UserInputText
from code.classes.Player import Player
from code.classes.Card import Card


def main():
	"""Runs the game.

	"""
	# Set up the database and tables if not already done.
	setup_db()

	conn = sqlite3.connect( general.DB_NAME )
	# Required to access values by column names.
	conn.row_factory = sqlite3.Row
	cursor = conn.cursor()

	# Fetch the current settings.
	cursor.execute( "SELECT * FROM %s" % general.DB_TABLE_SETTINGS )
	row_settings = cursor.fetchone()
		
	# Set up the main window.
	window = pygame.display.set_mode( general.WINDOW_DIMENSIONS )
	# Run the game at a constant framerate.
	clock = pygame.time.Clock()

	# Go into fullscreen mode?
	if enable_fullscreen( row_settings ):
		window = pygame.display.set_mode( general.WINDOW_DIMENSIONS, pygame.FULLSCREEN )
	

	# Now run the game.
	running = True
	
	# Show the main menu.
	main_menu( window, clock, conn, cursor )

	pygame.display.quit()


def main_menu( window, clock, conn, cursor ):
	"""Displays the main menu.

	"""
	texts = pygame.sprite.Group()
	buttons = pygame.sprite.Group()
	for item in main_menu_data.DATA.itervalues():
		if item["type"] == "button":
			buttons.add(
				Button( item["font"], item["text"], item["pos_center"], item["return_code"] )
			)
		elif item["type"] == "text":
			texts.add(
				Text( item["font"], item["text"], item["pos_center"] )
			)
	
	running = True
	while running:
	
		# Check for any events.
		eventlist = pygame.event.get()
		for event in eventlist:
			if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
				running = False
			elif event.type == pygame.QUIT:
				running = False
				
		# Blit everything.
		window.fill( colours.BLACK )
		texts.draw( window )
		buttons.draw( window )
		
		# Update everything to make the change appear.
		for b in buttons:
			if b.update():
				if b.return_code == return_codes.START_GAME:
					running_setup_amount_players = True
					while running_setup_amount_players:
						ret_code_players = pre_game_setup_amount_players( window,
							clock, conn, cursor )
						if ret_code_players == return_codes.RETURN:
							running_setup_amount_players = False
						else:
							running_setup_names_of_players = True
							while running_setup_names_of_players:
								players = pre_game_setup_names_of_players( window, clock, conn, cursor,
									ret_code_players )
								if players == False:
									running_setup_names_of_players = False
								else:
									running_setup_board_size = True
									while running_setup_board_size:
										board_size = pre_game_setup_board_size( window, clock, conn, cursor )
										if board_size == return_codes.RETURN:
											running_setup_board_size = False
											break
										else:
											game_start( window, clock, conn, cursor, players, board_size )
											running_setup_board_size = False
											running_setup_names_of_players = False
											running_setup_amount_players = False
				elif b.return_code == return_codes.OPTIONS:
					option_menu( window, clock, conn, cursor )
				elif b.return_code == return_codes.HIGHSCORES:
					pass
				elif b.return_code == return_codes.END_GAME:
					running = False
				
		pygame.display.update()

		clock.tick( general.FPS )


def game_start( window, clock, conn, cursor, players, board_size_code ):
	"""Initializes and runs the game.

	"""
	# First we set up the board.
	if board_size_code == return_codes.BOARDSIZE_FOUR_BY_FOUR:
		board_size = 4	
	elif board_size_code == return_codes.BOARDSIZE_SIX_BY_SIX:
		board_size = 6
	elif board_size_code == return_codes.BOARDSIZE_EIGHT_BY_EIGHT:
		board_size = 8
	elif board_size_code == return_codes.BOARDSIZE_TEN_BY_TEN:
		board_size = 10
	else:
		return

	pic_size = ( general.WINDOW_HEIGHT / board_size ) - 5
	
	# Load each picture twice.
	pictures = load_pictures( pic_size )
	random.shuffle( pictures )
	pictures = pictures[0:(board_size * board_size) / 2]

	pictures_with_ids = []
	for x in range( len( pictures ) ):
		pictures_with_ids.append( [ x, pictures[x] ] )
		pictures_with_ids.append( [ x, pictures[x] ] )

	random.shuffle( pictures_with_ids )
	
	col = 0
	row = 0
	picture_counter = 0
	
	cards = pygame.sprite.Group()
	for x in range( board_size * board_size ):
		if x % board_size == 0 and x != 0:
			row += 1
			col = 0
			
		cards.add(
			Card( row, col, pictures_with_ids[picture_counter], pic_size )
		)

		col += 1
		picture_counter += 1
		

	# Now run the game!
	card1_flipped = None
	card2_flipped = None
	
	running = True

	stay_open_timer = 0
	stay_open_timer_max = general.FPS
	stay_open_timer_on = False

	current_player = 0
	max_players = len( players )

	left_mouse_button_still_down = False
	game_over = False

	running = True
	while running:
		if len( cards ) > 0:
			# Check for any events.
			eventlist = pygame.event.get()
			for event in eventlist:
				if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE or event.type == pygame.QUIT:
					ret_val = game_paused( window, clock )
					if ret_val == False or ret_val == return_codes.GAME_PAUSED_END_GAME:
						running = False
					else:
						left_mouse_button_still_down = True
						
				if left_mouse_button_still_down and event.type == pygame.MOUSEBUTTONUP:
					left_mouse_button_still_down = False
						
			if running == False:
				break

			# Update everything.
			if not stay_open_timer_on:
				for card in cards:
					if not left_mouse_button_still_down and card.update():
						if card1_flipped == None:
							card1_flipped = card
						else:
							card2_flipped = card

			if card1_flipped != None and card2_flipped != None and stay_open_timer_on == False:
				stay_open_timer_on = True

				# Compare these two cards.
				if card1_flipped.id == card2_flipped.id:
					players[current_player].update_pairs()
				else:
					players[current_player].update_tries()
				players[current_player].update_hud()


			if stay_open_timer_on:
				if stay_open_timer_on and stay_open_timer < stay_open_timer_max:
					stay_open_timer += 1
				else:
					stay_open_timer_on = False
					stay_open_timer = 0			
				
					# Compare these two cards.
					if card1_flipped.id == card2_flipped.id:

						# A pair has been found.  Remove those cards.
						card1_flipped.kill()
						card2_flipped.kill()

						card1_flipped = None
						card2_flipped = None

					else:
						card1_flipped.image = card1_flipped.overlay
						card2_flipped.image = card2_flipped.overlay
						card1_flipped.flipped = False
						card2_flipped.flipped = False
						card1_flipped = None
						card2_flipped = None

						if current_player + 1 == max_players:
							current_player = 0
						else:
							current_player += 1


			# Blit everything.
			window.fill( colours.BLACK )
			cards.draw( window )


			players[current_player].texts.draw( window )

			pygame.display.update()

			clock.tick( general.FPS )
		else:
			running = False
			game_over = True

	if game_over:
		# Show the game over screen witht the highscores of each player.
		game_over_scene( window, clock, players )


def option_menu( window, clock, conn, cursor ):
	"""Displays the option menu

	"""
	texts = pygame.sprite.Group()
	buttons = pygame.sprite.Group()
	enable_disable_options = pygame.sprite.Group()
	
	for item in option_menu_data.DATA.itervalues():
		if item["type"] == "button":
			buttons.add(
				Button( item["font"], item["text"], item["pos_center"], item["return_code"] )
			)
		elif item["type"] == "text":
			texts.add(
				Text( item["font"], item["text"], item["pos_center"] )
			)
		elif item["type"] == "enable_disable_option":
			enable_disable_options.add(
				EnableDisableOption(
					Button( item["font"], item["text"], item["pos_center"], item["return_code"] ),
					conn,
					cursor,
					item["table"],
					item["column"]
				)
			)
	
	running = True
	while running:
	
		# Check for any events.
		eventlist = pygame.event.get()
		for event in eventlist:
			if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
				running = False
			elif event.type == pygame.QUIT:
				running = False
				
		# Blit everything.
		window.fill( colours.BLACK )
		texts.draw( window )
		buttons.draw( window )
		enable_disable_options.draw( window )
		for item in enable_disable_options:
			window.blit(
				item.enable_disable_option_image.image,
				item.enable_disable_option_image.rect
			)
		
		# Update everything to make the change appear.
		for b in buttons:
			if b.update():
				if b.return_code == return_codes.END_GAME:
					pygame.event.clear()
					running = False

		for b in enable_disable_options:
			if b.update():
				if b.button.return_code == return_codes.FULLSCREEN:
					if b.option_value == 1:
						window = pygame.display.set_mode( general.WINDOW_DIMENSIONS, pygame.FULLSCREEN )
					else:
						window = pygame.display.set_mode( general.WINDOW_DIMENSIONS )
				
		pygame.display.update()

		clock.tick( general.FPS )
			

def pre_game_setup_amount_players( window, clock, conn, cursor ):
	"""Displays the pre-game setup.

	"""
	texts = pygame.sprite.Group()
	buttons = pygame.sprite.Group()
	
	for item in pre_game_setup_data.DATA.itervalues():
		if item["type"] == "button":
			buttons.add(
				Button( item["font"], item["text"], item["pos_center"], item["return_code"] )
			)
		elif item["type"] == "text":
			texts.add(
				Text( item["font"], item["text"], item["pos_center"] )
			)

	players_return_codes = ( return_codes.ONEPLAYER, return_codes.TWOPLAYERS,
		return_codes.THREEPLAYERS, return_codes.FOURPLAYERS,
		return_codes.FIVEPLAYERS, return_codes.SIXPLAYERS
	)
				
	running = True
	while running:
	
		# Check for any events.
		eventlist = pygame.event.get()
		for event in eventlist:
			if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
				running = False
			elif event.type == pygame.QUIT:
				running = False
				
		# Blit everything.
		window.fill( colours.BLACK )
		texts.draw( window )
		buttons.draw( window )
		
		# Update everything to make the change appear.
		for b in buttons:
			if b.update():
				return b.return_code
				
		pygame.display.update()

		clock.tick( general.FPS )


def pre_game_setup_names_of_players( window, clock, conn, cursor,
	amount_players_return_code ):
	"""The user has to enter the names of the players in this 'dialog'.

	"""
	if amount_players_return_code == return_codes.ONEPLAYER:
		number_of_players = 1
	elif amount_players_return_code == return_codes.TWOPLAYERS:
		number_of_players = 2
	elif amount_players_return_code == return_codes.THREEPLAYERS:
		number_of_players = 3
	elif amount_players_return_code == return_codes.FOURPLAYERS:
		number_of_players = 4
	elif amount_players_return_code == return_codes.FIVEPLAYERS:
		number_of_players = 5
	else:
		number_of_players = 6


	texts = pygame.sprite.Group()
	buttons = pygame.sprite.Group()
	user_input_text = pygame.sprite.GroupSingle()

	player_name = ""
	lshift_pressed = False
	player_id = 1

	players = []

	chars_entered = 0

	for item in pre_game_setup_names_of_players_data.DATA.itervalues():
		if item["type"] == "button":
			buttons.add(
				Button( item["font"], item["text"], item["pos_center"], item["return_code"] )
			)
		elif item["type"] == "text":
			texts.add(
				Text( item["font"], item["text"], item["pos_center"] )
			)
		elif item["type"] == "user_input_text":
			default_player_name = item["text"]
			user_input_text.add(
				UserInputText( item["font"], item["text"], item["pos_center"] )
			)
					

	for x in range( number_of_players ):
		running = True
		player_name = default_player_name % player_id
		user_input_text.sprite.update_text( player_name )
		while running:
			eventlist = pygame.event.get()
			for event in eventlist:
				if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
					running = False
				elif event.type == pygame.QUIT:
					running = False
				elif event.type == pygame.KEYDOWN:
					if event.key == pygame.K_LSHIFT:
						lshift_pressed = True

				if event.type == pygame.KEYUP:
					if event.key == pygame.K_LSHIFT:
						lshift_pressed = False


				if event.type == pygame.KEYDOWN:
					if event.key >= 97 and event.key <= 122 or event.key == 32:
						if lshift_pressed:
							player_name += chr( event.key ).upper()
						else:
							player_name += chr( event.key )
						chars_entered += 1
						user_input_text.sprite.update_text( player_name )
						
					elif event.key == pygame.K_BACKSPACE and len( player_name ) >= len( default_player_name ):
						player_name = player_name[0:-1]
						user_input_text.sprite.update_text( player_name )
						chars_entered -= 1

			# Blit everything.
			window.fill( colours.BLACK )

			texts.draw( window )
			buttons.draw( window )
			user_input_text.draw( window )
		
			# Update everything to make the change appear.
			for b in buttons:
				if b.update():
					if b.return_code == return_codes.RETURN:
						return False
					elif b.return_code == return_codes.NEXTPLAYERNAME and chars_entered > 0:
						running = False
				
			pygame.display.update()

			clock.tick( general.FPS )

		# Create the player here.
		players.append(
			Player( player_id, player_name[len( default_player_name ) - 1:] )
		)

		# The id of the next player if another player is participating.
		player_id += 1

	return players


def pre_game_setup_board_size( window, clock, conn, cursor ):
	"""In this step, the players can choose the size of the board.

	"""
	texts = pygame.sprite.Group()
	buttons = pygame.sprite.Group()
	for item in pregame_setup_boardsize.DATA.itervalues():
		if item["type"] == "button":
			buttons.add(
				Button( item["font"], item["text"], item["pos_center"], item["return_code"] )
			)
		elif item["type"] == "text":
			texts.add(
				Text( item["font"], item["text"], item["pos_center"] )
			)
	
	running = True
	while running:
	
		# Check for any events.
		eventlist = pygame.event.get()
		for event in eventlist:
			if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
				running = False
			elif event.type == pygame.QUIT:
				running = False
		
		# Update everything to make the change appear.
		for b in buttons:
			if b.update():
				return b.return_code

		# Blit everything.
		window.fill( colours.BLACK )
		texts.draw( window )
		buttons.draw( window )					


		pygame.display.update()

		clock.tick( general.FPS )


def game_paused( window, clock ):
	texts = pygame.sprite.Group()
	buttons = pygame.sprite.Group()

	for item in setup_game_paused.DATA.itervalues():
		if item["type"] == "button":
			buttons.add(
				Button( item["font"], item["text"], item["pos_center"], item["return_code"] )
			)
		elif item["type"] == "text":
			texts.add(
				Text( item["font"], item["text"], item["pos_center"] )
			)
	
	while True:
		# Check for any events.
		eventlist = pygame.event.get()
		for event in eventlist:
			if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
				# Continue with the game.
				return False

		# Update buttons.
		for b in buttons:
			if b.update():
				return b.return_code

		# Blit everything
		window.fill( colours.BLACK )
		texts.draw( window )
		buttons.draw( window )

		pygame.display.update()
		clock.tick( general.FPS )
	return


def game_over_scene( window, clock, players ):
	"""Displays the scores of the players and is shown after a finished game of pairs.
	
	"""
	texts = pygame.sprite.Group()
	buttons = pygame.sprite.Group()

	counter = 1
	result_text = "%s: %d pairs, %d tries"

	for item in game_over_scene_data.DATA.itervalues():
		if item["type"] == "button":
			buttons.add(
				Button( item["font"], item["text"], item["pos_center"], item["return_code"] )
			)
		elif item["type"] == "text":
			# Display the next player's statistics.
			if len( item["text"] ) == 0:
			 	for p in players:
			 		if p.id == counter:
			 			text_to_display = result_text % ( p.name, p.pairs, p.tries )
						texts.add(
							Text( item["font"], text_to_display, item["pos_center"] )
						)
				counter += 1
			else:
				texts.add(
					Text( item["font"], item["text"], item["pos_center"] )
				)			


	running = True
	while running:
		# Check for any events.
		eventlist = pygame.event.get()
		for event in eventlist:
			if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
				# Continue with the game.
				running = False
			elif event.type == pygame.KEYDOWN and event.key == pygame.K_RETURN:
				running = False

		# Update buttons.
		for b in buttons:
			if b.update():
				running = False

		# Blit everything
		window.fill( colours.BLACK )
		texts.draw( window )
		buttons.draw( window )

		pygame.display.update()
		clock.tick( general.FPS )	


def setup_db():
	conn = sqlite3.connect( general.DB_NAME )
	cursor = conn.cursor()

	# Enable foreign key support.
	cursor.execute( "PRAGMA foreign_keys" )

	cursor.execute( general.DB_TABLE_SETTINGS_CREATE )
	cursor.execute( general.DB_TABLE_PLAYER_CREATE )
	cursor.execute( general.DB_TABLE_BOARD_SIZE_CREATE )
	cursor.execute( general.DB_TABLE_HIGHSCORE_CREATE )
	conn.commit()

	cursor.execute( """
		INSERT INTO %s
			(fullscreen, music, sound)
		VALUES
			(0, 1, 1);
	""" % general.DB_TABLE_SETTINGS )

	conn.commit()
	
	conn.close()


def enable_fullscreen( settings ):
	if settings["fullscreen"]:
		return True
	else:
		return False

def enable_music( settings ):
	if settings["music"]:
		return True
	else:
		return False
		
def enable_sound( settings ):
	if settings["sound"]:
		return True
	else:
		return False


def load_pictures( pixels ):
	"""This function loads all pictures and returns them.

	"""
	pictures = []
	for x in os.listdir( general.GFX_PATH ):
		if x == "." or x == ".." or x == "on.png" or x == "off.png":
			continue
		picture =  pygame.image.load(
			os.path.join(
				general.GFX_PATH, x
			)
		)
		picture = pygame.transform.smoothscale(
			picture,
			( pixels, pixels )
		)
		pictures.append( picture )


	return pictures
	

if __name__ == "__main__":
	main()
